/*
 * Copyright, Aspect Security, Inc.
 *
 * This file is part of JavaSnoop.
 *
 * JavaSnoop is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JavaSnoop is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with JavaSnoop.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.aspect.snoop.ui.tamper.bytearray;

import java.awt.event.ActionEvent;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

import org.jdesktop.application.Action;

import com.aspect.snoop.ui.tamper.common.EditStringView;
import com.aspect.snoop.util.IOUtil;
import com.aspect.snoop.util.UIUtil;

public class EditByteArrayView extends javax.swing.JDialog {

    private byte[] bytes;

    public byte[] getBytes() {
        return bytes;
    }

    HexTableCellEditor editor;

    public EditByteArrayView(java.awt.Frame parent, boolean modal, byte[] bytes) {
        super(parent, modal);
        initComponents();
        this.bytes = bytes;

        lblByteCount.setText(""+bytes.length);

        tblOriginalBytes.setCellSelectionEnabled(true);
        tblOriginalBytes.setModel( new ByteArrayTableModel(tblAsciiBytes, bytes) );
        tblOriginalBytes.setDefaultRenderer(Byte.class, new CenteredCellRenderer(tblAsciiBytes));
        tblOriginalBytes.setDragEnabled(false);

        TableSelectionHandler originalHandler = new TableSelectionHandler(tblOriginalBytes, tblAsciiBytes);
        tblOriginalBytes.getColumnModel().addColumnModelListener(originalHandler);
        tblOriginalBytes.getSelectionModel().addListSelectionListener(originalHandler);

        editor = new HexTableCellEditor();

        tblOriginalBytes.setDefaultEditor(Byte.class, editor);

        tblAsciiBytes.setDragEnabled(false);
        tblAsciiBytes.setCellSelectionEnabled(true);
        tblAsciiBytes.setModel ( new AsciiArrayTableModel(tblOriginalBytes, bytes) );
        tblAsciiBytes.setDefaultRenderer(Character.class, new CenteredCellRenderer(tblOriginalBytes));

        TableSelectionHandler asciiHandler = new TableSelectionHandler(tblAsciiBytes, tblOriginalBytes);
        tblAsciiBytes.getColumnModel().addColumnModelListener(asciiHandler);
        tblAsciiBytes.getSelectionModel().addListSelectionListener(asciiHandler);

        for(int i=0;i<16;i++) {
            tblOriginalBytes.getColumnModel().getColumn(i).setResizable(false);
            tblAsciiBytes.getColumnModel().getColumn(i).setResizable(false);
        }



    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        btnViewGroup = new javax.swing.ButtonGroup();
        originalPane = new javax.swing.JScrollPane();
        tblOriginalBytes = new javax.swing.JTable();
        jLabel1 = new javax.swing.JLabel();
        lblByteCount = new javax.swing.JLabel();
        btnReplaceWithString = new javax.swing.JButton();
        btnChangeByteArraySize = new javax.swing.JButton();
        jLabel3 = new javax.swing.JLabel();
        btnReplaceWithInt = new javax.swing.JButton();
        btnReplaceWithShort = new javax.swing.JButton();
        btnReplaceWithLong = new javax.swing.JButton();
        btnReplaceWithFloat = new javax.swing.JButton();
        btnReplaceWithDouble = new javax.swing.JButton();
        btnReplaceWithFile = new javax.swing.JButton();
        btnEditAsString = new javax.swing.JButton();
        asciiPane = new javax.swing.JScrollPane();
        tblAsciiBytes = new javax.swing.JTable();
        btnAcceptChanges = new javax.swing.JButton();
        rdoHex = new javax.swing.JRadioButton();
        rdoAscii = new javax.swing.JRadioButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(com.aspect.snoop.JavaSnoop.class).getContext().getResourceMap(EditByteArrayView.class);
        setTitle(resourceMap.getString("Form.title")); // NOI18N
        setName("Form"); // NOI18N
        setResizable(false);

        originalPane.setName("originalPane"); // NOI18N

        tblOriginalBytes.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null}
            },
            new String [] {
                "Title 1", "Title 2", "Title 3", "Title 4"
            }
        ));
        tblOriginalBytes.setName("tblOriginalBytes"); // NOI18N
        originalPane.setViewportView(tblOriginalBytes);

        jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N
        jLabel1.setName("jLabel1"); // NOI18N

        lblByteCount.setText(resourceMap.getString("lblByteCount.text")); // NOI18N
        lblByteCount.setName("lblByteCount"); // NOI18N

        javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance(com.aspect.snoop.JavaSnoop.class).getContext().getActionMap(EditByteArrayView.class, this);
        btnReplaceWithString.setAction(actionMap.get("replaceBytesAsString")); // NOI18N
        btnReplaceWithString.setText(resourceMap.getString("btnReplaceWithString.text")); // NOI18N
        btnReplaceWithString.setFocusable(false);
        btnReplaceWithString.setName("btnReplaceWithString"); // NOI18N

        btnChangeByteArraySize.setAction(actionMap.get("changeArrayByteSize")); // NOI18N
        btnChangeByteArraySize.setText(resourceMap.getString("btnChangeByteArraySize.text")); // NOI18N
        btnChangeByteArraySize.setFocusable(false);
        btnChangeByteArraySize.setName("btnChangeByteArraySize"); // NOI18N

        jLabel3.setText(resourceMap.getString("jLabel3.text")); // NOI18N
        jLabel3.setName("jLabel3"); // NOI18N

        btnReplaceWithInt.setAction(actionMap.get("replaceBytesAsNumber")); // NOI18N
        btnReplaceWithInt.setText(resourceMap.getString("btnReplaceWithInt.text")); // NOI18N
        btnReplaceWithInt.setFocusable(false);
        btnReplaceWithInt.setName("btnReplaceWithInt"); // NOI18N

        btnReplaceWithShort.setAction(actionMap.get("replaceBytesAsNumber")); // NOI18N
        btnReplaceWithShort.setText(resourceMap.getString("btnReplaceWithShort.text")); // NOI18N
        btnReplaceWithShort.setFocusable(false);
        btnReplaceWithShort.setName("btnReplaceWithShort"); // NOI18N

        btnReplaceWithLong.setAction(actionMap.get("replaceBytesAsNumber")); // NOI18N
        btnReplaceWithLong.setText(resourceMap.getString("btnReplaceWithLong.text")); // NOI18N
        btnReplaceWithLong.setFocusable(false);
        btnReplaceWithLong.setName("btnReplaceWithLong"); // NOI18N

        btnReplaceWithFloat.setAction(actionMap.get("replaceBytesAsNumber")); // NOI18N
        btnReplaceWithFloat.setText(resourceMap.getString("btnReplaceWithFloat.text")); // NOI18N
        btnReplaceWithFloat.setFocusable(false);
        btnReplaceWithFloat.setName("btnReplaceWithFloat"); // NOI18N

        btnReplaceWithDouble.setAction(actionMap.get("replaceBytesAsNumber")); // NOI18N
        btnReplaceWithDouble.setText(resourceMap.getString("btnReplaceWithDouble.text")); // NOI18N
        btnReplaceWithDouble.setFocusable(false);
        btnReplaceWithDouble.setName("btnReplaceWithDouble"); // NOI18N

        btnReplaceWithFile.setAction(actionMap.get("replaceBytesFromFile")); // NOI18N
        btnReplaceWithFile.setText(resourceMap.getString("btnReplaceWithFile.text")); // NOI18N
        btnReplaceWithFile.setFocusable(false);
        btnReplaceWithFile.setName("btnReplaceWithFile"); // NOI18N

        btnEditAsString.setAction(actionMap.get("editBytesAsString")); // NOI18N
        btnEditAsString.setText(resourceMap.getString("btnEditAsString.text")); // NOI18N
        btnEditAsString.setFocusable(false);
        btnEditAsString.setName("btnEditAsString"); // NOI18N

        asciiPane.setName("asciiPane"); // NOI18N

        tblAsciiBytes.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null},
                {null, null, null, null}
            },
            new String [] {
                "Title 1", "Title 2", "Title 3", "Title 4"
            }
        ));
        tblAsciiBytes.setName("tblAsciiBytes"); // NOI18N
        asciiPane.setViewportView(tblAsciiBytes);

        btnAcceptChanges.setText(resourceMap.getString("btnAcceptChanges.text")); // NOI18N
        btnAcceptChanges.setName("btnAcceptChanges"); // NOI18N
        btnAcceptChanges.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnAcceptChangesActionPerformed(evt);
            }
        });

        rdoHex.setAction(actionMap.get("changeView")); // NOI18N
        btnViewGroup.add(rdoHex);
        rdoHex.setSelected(true);
        rdoHex.setText(resourceMap.getString("rdoHex.text")); // NOI18N
        rdoHex.setName("rdoHex"); // NOI18N

        rdoAscii.setAction(actionMap.get("changeView")); // NOI18N
        btnViewGroup.add(rdoAscii);
        rdoAscii.setText(resourceMap.getString("rdoAscii.text")); // NOI18N
        rdoAscii.setName("rdoAscii"); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                            .addComponent(btnEditAsString, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                            .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
                                .addComponent(jLabel1)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                .addComponent(lblByteCount, javax.swing.GroupLayout.PREFERRED_SIZE, 65, javax.swing.GroupLayout.PREFERRED_SIZE))
                            .addComponent(btnChangeByteArraySize, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createSequentialGroup()
                                .addGap(10, 10, 10)
                                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                                        .addComponent(btnReplaceWithString, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                        .addComponent(btnReplaceWithShort))
                                    .addComponent(jLabel3, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 136, javax.swing.GroupLayout.PREFERRED_SIZE))
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(btnReplaceWithInt)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(btnReplaceWithLong)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(btnReplaceWithFloat)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(btnReplaceWithDouble)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(btnReplaceWithFile))
                            .addGroup(layout.createSequentialGroup()
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                .addComponent(rdoHex)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                .addComponent(rdoAscii))))
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(originalPane, javax.swing.GroupLayout.PREFERRED_SIZE, 428, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(asciiPane, javax.swing.GroupLayout.PREFERRED_SIZE, 430, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap(767, Short.MAX_VALUE)
                .addComponent(btnAcceptChanges)
                .addContainerGap())
        );

        layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {asciiPane, originalPane});

        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(jLabel1)
                            .addComponent(lblByteCount))
                        .addGap(35, 35, 35))
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addComponent(jLabel3)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(btnReplaceWithString)
                            .addComponent(btnReplaceWithInt)
                            .addComponent(btnReplaceWithLong)
                            .addComponent(btnChangeByteArraySize)
                            .addComponent(btnReplaceWithShort)
                            .addComponent(btnReplaceWithFloat)
                            .addComponent(btnReplaceWithDouble)
                            .addComponent(btnReplaceWithFile))))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(btnEditAsString)
                    .addComponent(rdoHex)
                    .addComponent(rdoAscii))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addComponent(asciiPane, 0, 0, Short.MAX_VALUE)
                    .addComponent(originalPane, javax.swing.GroupLayout.DEFAULT_SIZE, 210, Short.MAX_VALUE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(btnAcceptChanges)
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void btnAcceptChangesActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnAcceptChangesActionPerformed
        dispose();
    }//GEN-LAST:event_btnAcceptChangesActionPerformed

    /**
    * @param args the command line arguments
    */
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                EditByteArrayView dialog = new EditByteArrayView(new javax.swing.JFrame(), true, "foobar".getBytes());
                dialog.addWindowListener(new java.awt.event.WindowAdapter() {
                    public void windowClosing(java.awt.event.WindowEvent e) {
                        System.exit(0);
                    }
                });
                dialog.setVisible(true);
            }
        });
    }

    @Action
    public void changeArrayByteSize() {
        String num = JOptionPane.showInputDialog("Enter new array size: ");

        if ( num != null ) {
            int oldSize = bytes.length;
            int newSize = Integer.parseInt(num);
            byte[] newArray = new byte[newSize];

            if ( newSize >= oldSize ) {
                for(int i=0;i<oldSize;i++) {
                    newArray[i] = bytes[i];
                }
                for(int i=oldSize;i<newSize;i++) {
                    newArray[i] = 0;
                }

            } else {

                for(int i=0;i<newSize;i++) {
                    newArray[i] = bytes[i];
                }

            }

            bytes = newArray;
            updateTable();
        }

    }

    @Action
    public void editBytesAsString() {

        EditStringView view = new EditStringView(this, true, "");
        view.setVisible(true);

        UIUtil.waitForInput(view);

        bytes = view.getText().getBytes();
        updateTable();

    }

    @Action
    public void replaceBytesAsString() {

        String s = new String(bytes);

        EditStringView view = new EditStringView(this, true, s);
        view.setVisible(true);

        UIUtil.waitForInput(view);

        bytes = view.getText().getBytes();
        updateTable();

    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JScrollPane asciiPane;
    private javax.swing.JButton btnAcceptChanges;
    private javax.swing.JButton btnChangeByteArraySize;
    private javax.swing.JButton btnEditAsString;
    private javax.swing.JButton btnReplaceWithDouble;
    private javax.swing.JButton btnReplaceWithFile;
    private javax.swing.JButton btnReplaceWithFloat;
    private javax.swing.JButton btnReplaceWithInt;
    private javax.swing.JButton btnReplaceWithLong;
    private javax.swing.JButton btnReplaceWithShort;
    private javax.swing.JButton btnReplaceWithString;
    private javax.swing.ButtonGroup btnViewGroup;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel lblByteCount;
    private javax.swing.JScrollPane originalPane;
    private javax.swing.JRadioButton rdoAscii;
    private javax.swing.JRadioButton rdoHex;
    private javax.swing.JTable tblAsciiBytes;
    private javax.swing.JTable tblOriginalBytes;
    // End of variables declaration//GEN-END:variables

    @Action
    public void replaceBytesAsNumber(ActionEvent evt) {

        String num = JOptionPane.showInputDialog("Enter new value: ");

        if ( num == null )
            return;

        if ( evt.getActionCommand().equals("short") ) {

            Short s = Short.parseShort(num);
            bytes = shortToByteArray(s);

        } else if ( evt.getActionCommand().equals("int") ) {

            Integer s = Integer.parseInt(num);
            bytes = intToByteArray(s);

        } else if ( evt.getActionCommand().equals("long") ) {

            Long s = Long.parseLong(num);
            bytes = longToByteArray(s);

        } else if ( evt.getActionCommand().equals("double") ) {

            Double s = Double.parseDouble(num);
            bytes = doubleToByteArray(s);

        } else if ( evt.getActionCommand().equals("float") ) {

            Float s = Float.parseFloat(num);
            bytes = floatToByteArray(s);

        }

        updateTable();

    }

    public static byte[] shortToByteArray(short value) {
        return new byte[] {
                (byte)(value >>> 8),
                (byte)value};
    }

    public static byte[] intToByteArray(int value) {
        return new byte[] {
                (byte)(value >>> 24),
                (byte)(value >>> 16),
                (byte)(value >>> 8),
                (byte)value};
    }

    public static byte[] longToByteArray(long value) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);
        try {
            dos.writeLong(value);
        } catch (IOException ex) {
            // if this fails we have much bigger issues than lost integrity here
        }
        return baos.toByteArray();
    }

    public static byte[] floatToByteArray(float value) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);
        try {
            dos.writeFloat(value);
        } catch (IOException ex) {
            // if this fails we have much bigger issues than lost integrity here
        }
        return baos.toByteArray();
    }

    public static byte[] doubleToByteArray(double value) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);
        try {
            dos.writeDouble(value);
        } catch (IOException ex) {
            // if this fails we have much bigger issues than lost integrity here
        }
        return baos.toByteArray();
    }

    static File lastFile = null;
    @Action
    public void replaceBytesFromFile() {

        JFileChooser fc;

        if ( lastFile != null ) {
            fc = new JFileChooser(lastFile.getParentFile());
        } else {
            fc = new JFileChooser();
        }

        int rc = fc.showOpenDialog(this);

        if (rc == JFileChooser.APPROVE_OPTION) {

            lastFile = fc.getSelectedFile();

            try {

                bytes = IOUtil.getBytesFromFile(lastFile);
                updateTable();

            } catch (IOException ex) {
                UIUtil.showErrorMessage(this, "Error reading file: " + ex.getMessage());
            }
        }



    }

    private void updateTable() {
        AsciiArrayTableModel modelA = (AsciiArrayTableModel)tblAsciiBytes.getModel();
        ByteArrayTableModel modelB = (ByteArrayTableModel)tblOriginalBytes.getModel();

        modelA.setBytes(bytes);
        modelB.setBytes(bytes);

        tblAsciiBytes.updateUI();
        tblOriginalBytes.updateUI();

        tblAsciiBytes.repaint();
        tblOriginalBytes.repaint();

    }

    @Action
    public void changeView(ActionEvent evt) {

        ByteArrayTableModel model = (ByteArrayTableModel)tblOriginalBytes.getModel();

        if ( evt.getActionCommand().startsWith("Hex")) {
            model.showInHex(true);
            editor.shouldEditInHex(true);
        } else {
            model.showInHex(false);
            editor.shouldEditInHex(false);
        }

        tblOriginalBytes.repaint();


    }

}
